package com.itmsoft.test.utils;

import java.lang.reflect.Field;
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.lang.StringUtils;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.ejb.packaging.PersistenceMetadata;
import org.hibernate.ejb.packaging.PersistenceXmlLoader;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.junit.rules.ExternalResource;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;

import com.itmsoft.util.Constants;
import com.itmsoft.util.Services;

@SuppressWarnings("deprecation")
public class MockContainer extends ExternalResource {
    private static InitialContext ctx;
    private String servicesRootFolder;

    public MockContainer(Object testInstance) {
        this(testInstance, StringUtils.EMPTY);
    }

    public MockContainer(Object testInstance, String servicesRootFolder) {
        this.servicesRootFolder = servicesRootFolder;
        try {
            initOpenEJBConfig();
            ctx = new InitialContext();
            fillDatabaseTables();
            autowireFields(testInstance);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to start Mock container.", e);
        }
    }

    public <T> T autowireFields(final T testInstance) throws IllegalAccessException, NamingException {
        ReflectionUtils.doWithFields(testInstance.getClass(), new FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                field.setAccessible(true);
                Class<?> type = field.getType();
                if (type.equals(this.getClass())) {
                    field.set(testInstance, this);
                } else if (Services.servicesMap.containsKey(type)) {
                    try {
                        field.set(testInstance, getEJBBean(type));
                    } catch (NamingException e) {
                        throw new IllegalArgumentException(e);
                    }
                } else if (type.equals(InitialContext.class)) {
                    field.set(testInstance, ctx);
                }
            }
        });
        return testInstance;
    }

    @SuppressWarnings("unchecked")
    public <T> T getEJBBean(Class<T> beanClass) throws NamingException {
        return (T) ctx.lookup(Services.servicesMap.get(beanClass));
    }

    private void fillDatabaseTables() throws Exception {
        DataSource adminDs = (DataSource) ctx.lookup(Constants.Jndi.DATASOURCE);
        QueryRunner qr = new QueryRunner(adminDs);
        try {
            qr.query("SELECT * FROM STUFF WHERE ID = 0", new ResultSetHandler<String>() {
                @Override
                public String handle(ResultSet rs) throws SQLException {
                    // do nothing...
                    return null;
                }
            });
        } catch (SQLException e) {
            // assume we don't have a table...
            // lets load it then...
            initPersistenceConfigTo("jtUnit", Constants.Jndi.DATASOURCE);
        }
    }

    public void initOpenEJBConfig() {
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
        System.setProperty(Context.URL_PKG_PREFIXES, StringUtils.substringBeforeLast(MockContainer.class.getName(), "."));
        System.setProperty("openejb.configuration", servicesRootFolder + "target/test-classes/openejb.xml");
        System.setProperty("openejb.vendor.config", "geronimo");
        System.setProperty("openejb.log.factory", "org.apache.openejb.util.Slf4jLogStreamFactory");
        System.setProperty("openejb.logger.external", "true");
        // System.setProperty("openejb.jdbc.log", "true");
    }

    private static void initPersistenceConfigTo(String puName, String dbJndi) throws Exception {
        Ejb3Configuration cfg = new Ejb3Configuration();
        Enumeration<URL> xmls = Thread.currentThread().getContextClassLoader().getResources("META-INF/itm-persistence.xml");
        URL url = xmls.nextElement();
        List<PersistenceMetadata> metadataFiles = PersistenceXmlLoader.deploy(url, Collections.EMPTY_MAP, cfg.getHibernateConfiguration()
                .getEntityResolver(), PersistenceUnitTransactionType.JTA);
        for (PersistenceMetadata persistenceMetadata : metadataFiles) {
            if (!persistenceMetadata.getName().equals(puName)) {
                continue;
            }
            Map<String, String> override = new HashMap<String, String>();
            override.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
            persistenceMetadata.setJtaDatasource(dbJndi);
            cfg.configure(persistenceMetadata, override);
            System.out.println("Loading " + persistenceMetadata.getName() + " database to " + dbJndi + "...");
            SchemaUpdate schemaUpdate = new SchemaUpdate(cfg.getHibernateConfiguration());
            schemaUpdate.setDelimiter(";");
            schemaUpdate.execute(true, true);
            schemaUpdate.setHaltOnError(true);
            System.out.println("Successful.");
        }
    }
}
